﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using DotSpatial.Topology;
using DotSpatial.Data;

namespace GeostatisticalTool.Classes
{
    public class SearchData
    {

        private List<Kpoint> kpoints;
        private List<Kpoint> selectedPoints;



        private QuadTree quadTreeValues;


        private double initialDistanceSearch;
        private int type = 1; // 1 sector /4 sectors
        private double azimuth = 0;
        private double majorRadio;
        private double minorRadio;




        private int minPointsPerSector;
        private int maxPointsPerSector;

        double mult;
        private Kpoint focu1;
        private Kpoint focu2;
        private double distConst;
        private List<KDistance>[] ListToCheck;


        public List<Kpoint> SelectedPoints
        {
            get { return selectedPoints; }
            set { selectedPoints = value; }
        }

        public double InitialDistanceSearch
        {
            get { return initialDistanceSearch; }
            set { initialDistanceSearch = value; }
        }

        public int Type
        {
            get { return type; }
            set { type = value;
            ListToCheck = new List<KDistance>[type];
            for (int i = 0; i < type; i++)
            {
                ListToCheck[i] = new List<KDistance>();
            }
            }
        }

        public void SetParameters(int minPointsPerSector, int maxPointsPerSector)
        {
            this.minPointsPerSector = minPointsPerSector;
            this.maxPointsPerSector = maxPointsPerSector;
        }

        public void SetAniso(double azimuth, double minRadius, double maxRadius)
        {
            this.azimuth = azimuth;
            this.minorRadio = minRadius;
            this.majorRadio = maxRadius;
        }

        public List<Kpoint> Kpoints
        {
            get { return kpoints; }
            set { kpoints = value; }
        }

        public QuadTree QuadTreeValues
        {
            get { return quadTreeValues; }
            set { quadTreeValues = value; }
        }

        public double Azimuth
        {
            get { return azimuth; }
            set { azimuth = value; }
        }



        public SearchData()
        {

        }
        public SearchData(List<Kpoint> points, QuadTree quadTree,double[] extent)

        {
            this.quadTreeValues = quadTree;
            this.kpoints = points;
            this.minPointsPerSector = 2;
            this.maxPointsPerSector = 5;
            this.Type = 1;
            this.InitialDistanceSearch = (extent[3] - extent[0]) * 0.5;
            this.majorRadio = initialDistanceSearch;
            this.minorRadio = initialDistanceSearch;
        }

        private bool Conditions(Kpoint center,List<Kpoint> points,bool include)
        {
            List<Kpoint> def= new List<Kpoint>();
            foreach(Kpoint value in points)
               CheckSector(center,value,include);

            int num = 0;
            for( int i=0; i<type; i++)
            {
                num += ListToCheck[i].Count;
                ListToCheck[i].Sort();
            }

            if (minPointsPerSector == 0)
                return false;

            if (points.Count < minPointsPerSector * type)
                return true;




            if (num >= minPointsPerSector * type)
                return false;
            else
                return true;
              
        
        }

        /// <summary>
        /// Searching the points around of a position
        /// </summary>
        /// <param name="cpointsF">Point founds</param>
        /// <param name="nPn">Number of point searched</param>
        /// <param name="x">Coordinate X of the point to calculate</param>
        /// <param name="y">Coordinate Y of the point to calculate</param>
        public void ListPointAvailables(
            out List<Kpoint> cpointsF, double x, double y, double z, bool includePoint)
        {
             Kpoint center = new Kpoint(x, y, z, 0);
            if (kpoints.Count <= type * minPointsPerSector )
            {
                if (includePoint)
                    cpointsF = kpoints;
                else
                {
                    cpointsF = new List<Kpoint>();
                    foreach (Kpoint k in kpoints)
                    { 
                        if (!(k==center))
                            cpointsF.Add( k);
                    }
                    return;
                }

            }


           

           // double searchDistance = (this.extent[2] - this.extent[0]) * 0.05;
            double[] v = zone(center);
            double xsize=v[0]/2;
            double ysize = v[1]/2;
            double zsize = v[2]/2;
            double xmin=double.NaN;
            double ymin=double.NaN;
            double zmin=double.NaN;
            double xmax=double.NaN;
            double ymax = double.NaN;
            double zmax=double.NaN;
         

            int jj = 0;
            double dmult=0.1;
            do
            {
                ListToCheck = new List<KDistance>[type];
                for (int i = 0; i < type; i++)
                {
                    ListToCheck[i] = new List<KDistance>();
                }

                mult = jj * dmult;
                this.quadTreeValues.Clean();
                xmin = x - (xsize * (1 + mult ));
                ymin = y - (xsize * (1 + mult));
                zmin = y - (zsize * (1 + mult));
                xmax = x + (ysize * (1 + mult));
                ymax = y + (ysize * (1 + mult));
                zmax = y + (zsize * (1 + mult));

                Interval intX = new Interval(xmin, xmax);
                Interval intY = new Interval(ymin, ymax);
                Interval intZ = new Interval(zmin, zmax);
                Interval2D rect = new Interval2D(intX, intY,intZ);
                this.quadTreeValues.Query(rect);
                dmult++;
                jj++;
                if (jj>1)
                     v[1]=0;
                ConfigEllipse(center);
            }
            while (Conditions(center,this.quadTreeValues.GetData(),includePoint) && (v[0]*5) > Math.Abs(xmax-xmin) );

            cpointsF = new List<Kpoint>();
            for (int i = 0; i < type; i++)
            {  int val=evaluteNumPoints(jj,ListToCheck[i].Count);
                for (int j = 0; j < ListToCheck[i].Count && j < val ; j++)
                    cpointsF.Add(ListToCheck[i][j].point);
            }
            selectedPoints = cpointsF;

        }
        private int evaluteNumPoints(int loop,int numPointFoundPerSector)
        {


            if (minPointsPerSector == 0 && numPointFoundPerSector >= maxPointsPerSector)
            {
                return maxPointsPerSector;
 
            }

            if (minPointsPerSector == 0 && numPointFoundPerSector <= maxPointsPerSector)
            {
                return numPointFoundPerSector;

            }


            if (loop == 1 && numPointFoundPerSector <= maxPointsPerSector)
            {
                return numPointFoundPerSector;
            }


            if (loop == 1 && numPointFoundPerSector >= maxPointsPerSector)
            {
                return maxPointsPerSector;
            }


            if (loop > 1 &&  numPointFoundPerSector >= minPointsPerSector && numPointFoundPerSector <= maxPointsPerSector)
            {
                return minPointsPerSector;
            }


            if (loop > 1 && numPointFoundPerSector >= maxPointsPerSector)
            {
                return minPointsPerSector;
            }

            return minPointsPerSector;
        }

        public double azimut2= Math.PI/2;
        public double zRadio =0 ;

        private double MainRange()
        {

            return Math.Sqrt(this.majorRadio * this.majorRadio + this.minorRadio * this.minorRadio + this.zRadio * this.zRadio);
        }

        private double[] zone(Kpoint center)
        {
            #region zone

            Kpoint p1 = Utils.AzimuthDist(center, new double[]{this.azimuth,azimut2}, this.majorRadio);

            Kpoint p1R = Utils.AzimuthDist(p1, new double[]{this.azimuth+ Math.PI / 2,azimut2}, this.minorRadio);
            Kpoint p1L = Utils.AzimuthDist(p1, new double[]{this.azimuth - Math.PI / 2,azimut2}, this.minorRadio);

            Kpoint p2 = Utils.AzimuthDist(center,new double[]{ this.azimuth,azimut2}, this.majorRadio * -1);
            Kpoint p2R = Utils.AzimuthDist(p2,new double[]{ this.azimuth + Math.PI / 2,azimut2}, this.minorRadio);
            Kpoint p2L = Utils.AzimuthDist(p2, new double[] { this.azimuth - Math.PI / 2, azimut2 }, this.minorRadio);

            Kpoint p1R1 = Utils.AzimuthDist(p1R, new double[] { this.azimuth + Math.PI / 2, azimut2 + Math.PI }, this.zRadio);
            Kpoint p1R2 = Utils.AzimuthDist(p1R, new double[] { this.azimuth + Math.PI / 2, azimut2 - Math.PI }, this.zRadio);

            Kpoint p1L1 = Utils.AzimuthDist(p1L, new double[] { this.azimuth - Math.PI / 2, azimut2 + Math.PI }, this.zRadio);
            Kpoint p1L2 = Utils.AzimuthDist(p1L, new double[] { this.azimuth - Math.PI / 2, azimut2 - Math.PI }, this.zRadio);

            Kpoint p2R1 = Utils.AzimuthDist(p2R, new double[] { this.azimuth + Math.PI / 2, azimut2 + Math.PI }, this.zRadio);
            Kpoint p2R2 = Utils.AzimuthDist(p2R, new double[] { this.azimuth + Math.PI / 2, azimut2 - Math.PI }, this.zRadio);
            Kpoint p2L1 = Utils.AzimuthDist(p2L, new double[] { this.azimuth - Math.PI / 2, azimut2 + Math.PI }, this.zRadio);
            Kpoint p2L2 = Utils.AzimuthDist(p2L, new double[] { this.azimuth - Math.PI / 2, azimut2 - Math.PI }, this.zRadio);

           // Kpoint p3 = Utils.AzimuthDist(center, this.azimuth + Math.PI / 2, this.minorRadio);
          //  Kpoint p4 = Utils.AzimuthDist(center, this.azimuth + Math.PI / 2, this.minorRadio * -1);


            Stat elv = new Stat(false);
            elv += new Stat(p1R.Z);
            elv += new Stat(p1L.Z);
            elv += new Stat(p2R.Z);
            elv += new Stat(p2L.Z);
            elv += new Stat(p1R1.Z);
            elv += new Stat(p1R2.Z);
            elv += new Stat(p1L1.Z);
            elv += new Stat(p1L2.Z);
            elv += new Stat(p2R1.Z);
            elv += new Stat(p2R2.Z);
            elv += new Stat(p2L1.Z);
            elv += new Stat(p2L2.Z);

            Stat east = new Stat(false);
            east += new Stat(p1R.X);
            east += new Stat(p1L.X);
            east += new Stat(p2R.X);
            east += new Stat(p2L.X);
            east += new Stat(p1R1.X);
            east += new Stat(p1R2.X);
            east += new Stat(p1L1.X);
            east += new Stat(p1L2.X);
            east += new Stat(p2R1.X);
            east += new Stat(p2R2.X);
            east += new Stat(p2L1.X);
            east += new Stat(p2L2.X);

            Stat north = new Stat(false);
            north += new Stat(p1R.Y);
            north += new Stat(p1L.Y);
            north += new Stat(p2R.Y);
            north += new Stat(p2L.Y);
            north += new Stat(p1R1.Y);
            north += new Stat(p1R2.Y);
            north += new Stat(p1L1.Y);
            north += new Stat(p1L2.Y);
            north += new Stat(p2R1.Y);
            north += new Stat(p2R2.Y);
            north += new Stat(p2L1.Y);
            north += new Stat(p2L2.Y);
            #endregion
            return new double[3] { east.Max - east.Min, north.Max - north.Min, elv.Max-elv.Min };
        }

        private void  ConfigEllipse(Kpoint center)
        {
            double majorRadio = this.majorRadio * (1 + mult);
            double minorRadio = this.minorRadio * (1 + mult);
             double f = Math.Sqrt((majorRadio * majorRadio) - (minorRadio * minorRadio));
            focu1 = Utils.AzimuthDist(center, azimuth, f);
           // Kpoint pointRadioMajor = AzimuthDist(center, azimuth, majorRadio);
            focu2 = Utils.AzimuthDist(center, azimuth, -1 * f);
            distConst = 2 * majorRadio; //GetDistance(focu2,pointRadioMajor) + GetDistance(focu1,pointRadioMajor);

        }

        private void CheckSector(Kpoint center, Kpoint target,bool include)
        {
            if (center == target && !include)
                return;
            double majorRadio = this.majorRadio * (1 + mult);
            double minorRadio = this.minorRadio * (1 + mult);
            double dist = Utils.GetDistance(target, center);
            double distE = Utils.GetDistance(focu2, target) + Utils.GetDistance(focu1, target);
            double az = Utils.GetAzimuth(center, target);
            if (type == 1)
            {
                if (minorRadio==majorRadio && dist > majorRadio)
                    return;
                if (minorRadio != majorRadio && distE > distConst)
                    return;
  
                ListToCheck[0].Add(new KDistance(dist,az,target));
                    return;

           }else
            {
                if (minorRadio == majorRadio && dist > majorRadio)
                    return;

                if (minorRadio != majorRadio && distE > distConst)
                    return;

                int subtype = 0;
                double cycle = 2.0 * Math.PI;
                for (double i = 0; i < 2 * Math.PI; i += (cycle / type))
                {
                    //if ((azimuth + i) < az && az <= (azimuth + (i + (cycle / type)))
                    //    ||
                    //    ((azimuth + i) < (az+ cycle) && (az+ cycle) <= ((azimuth + (i + (cycle / type))) + cycle))
                    //    )
                    if (VerifyRegion((azimuth + i),(azimuth + (i + (cycle / type))),az))
                    {
                        ListToCheck[subtype].Add(new KDistance(dist, az, target));
                    }
                    subtype++;
                }
            }
       
        }

        public bool VerifyRegion(double az1, double az2, double target)
        {

            double az1V = Utils.VerifyAzimuth(az1);
            double az2V = Utils.VerifyAzimuth(az2);
            double cycle = 2.0 * Math.PI;
            if (target == az2)
                return true;

            if (az1V > az2V)
            {
                if (target > (3.0 * Math.PI / 2) && target> az1V)
                 return az1V < (target ) && (target) <= (az2V + cycle);
                else
                return az1V < (target + cycle) && (target + cycle) <= (az2V + cycle);
            }
            else
            {
                return az1V < target && target <= az2V;

            }
        }

        public List<IGeometry>[] ZoneSelected(IFeatureSet reference, double x, double y, double z)
        {

            //FeatureSet border = new FeatureSet(DotSpatial.Topology.FeatureType.Polygon);
            //border.Projection = reference.Projection;
            List<IGeometry>[] list=null;
            if (this.type == 1)
            {
                list = new List<IGeometry>[1];
                list[0] = new List<IGeometry>() { Utils.DrawEllipse(new Kpoint(x, y,z, 0), this.majorRadio, this.minorRadio, this.azimuth) };
            }

            if (this.type == 4)
            {
                list = new List<IGeometry>[2];
                list[0] = new List<IGeometry>() { Utils.DrawEllipse(new Kpoint(x, y,z, 0), this.majorRadio, this.minorRadio, this.azimuth) };
                list[1] =  Utils.DrawAxisEllipse(new Kpoint(x, y,z, 0), this.majorRadio, this.minorRadio, this.azimuth) ;
            }
            return list;
           
        }
    }
}
